/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package de.saar.penguin.give2.mapviewer;

import give.formula.Constant;
import give.formula.Orientation;
import give.formula.Position;
import give.formula.Term;
import give.util.GeometryTools;
import give.world.World;
import give.world.WorldMap;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

/**
 * @author koller
 */
public class JMapViewer extends JPanel implements MouseMotionListener {

    private static final int GRID = 50;
    private static final int LABELWIDTH = 18;           // distance between labels and map
    private static final int LABELHEIGHT = LABELWIDTH;
    private static final Color ROOM_GRAY = new Color(0x0e0e0e0);
    private static final Color ROOM_RED = new Color(0x0ffe0e0);
    private static final Color ROOM_BLUE = new Color(0x0e0e0ff);
    private static final Color ROOM_GREEN = new Color(0x0e0ffe0);
    private static final Color CLOSED_DOOR = new Color(0x0808080); // between ROOM_GRAY and black
    private static final Color OPEN_DOOR = new Color(0x0f4f4f4);   // between ROOM_GRAY and white
    private static final Color ALARM_COLOR_ON = new Color(1, 0, 0, 0.5f);
    private static final Color ALARM_COLOR_OFF = new Color(0.2f, 0.2f, 0, 0.5f);
    private static final Color UNKNOWN_OBJECT_COLOR = new Color(0xaaaaee);
    private static final double PLAYER_IMAGE_SCALE = 0.3;
    private BufferedImage playerImg = readFromResource("icons/player.png");
    private BufferedImage flowerImg = readFromResource("icons/Plant.gif");
    private BufferedImage chairImg = readFromResource("icons/Chair.gif");
    private BufferedImage couchImg = readFromResource("icons/Couch.gif");
    private BufferedImage lampImg = readFromResource("icons/Lamp.gif");
    private BufferedImage trophyImg = readFromResource("icons/Trophy.png");
    private BufferedImage tableImg = readFromResource("icons/Table.gif");
    private BufferedImage dresserImg = readFromResource("icons/Dresser.gif");
    private BufferedImage bedImg = readFromResource("icons/Bed.gif");
    final private World world;
    private WorldMap map;
    private List<NamedShape> shapes;
    private Map<String, Rectangle2D> boundingBoxesForObjectNames;
    private Set<String> previousObjectsUnderMouse, currentObjectsUnderMouse;
    private List<MouseOverObjectListener> mouseOverObjectListeners;
    private List<MapAnnotationPlugin> annotationPlugins;
    private boolean showAxisLabels;
    private boolean showPlayerIcon;
    private Map<String, Integer> objectTypePaintingOrder;
    private List<String> sortedObjectsInWorld;
    private final int internalMapWidth;
    private final int internalMapHeight;
    private AffineTransform currentTransform;
    private boolean alignWithPlayer, centerOnPlayer;
    private double widthFactor, heightFactor;

    public JMapViewer(World world, double widthFactor, double heightFactor) {
        this.world = world;
        this.widthFactor = widthFactor;
        this.heightFactor = heightFactor;

        this.map = world.getMap();
        showAxisLabels = true;
        showPlayerIcon = true;

        shapes = new ArrayList<NamedShape>();
        boundingBoxesForObjectNames = new HashMap<String, Rectangle2D>();
        previousObjectsUnderMouse = new HashSet<String>();
        currentObjectsUnderMouse = new HashSet<String>();

        internalMapWidth = GRID * (map.getWidth() + 1);
        internalMapHeight = GRID * (map.getHeight() + 1);
        Dimension dim = new Dimension((int) (internalMapWidth * widthFactor), (int) (internalMapHeight * heightFactor));

        setPreferredSize(dim);
        setMinimumSize(dim);

        mouseOverObjectListeners = new ArrayList<MouseOverObjectListener>();
        addMouseMotionListener(this);

        annotationPlugins = new ArrayList<MapAnnotationPlugin>();
        currentTransform = new AffineTransform();
        alignWithPlayer = false;
        centerOnPlayer = false;

        // retrieve all objects from the universe and sort them in the correct order for painting
        objectTypePaintingOrder = new HashMap<String, Integer>();
        objectTypePaintingOrder.put("door", 1);
        objectTypePaintingOrder.put("wall", 2);
        objectTypePaintingOrder.put("other", 3);

        Collection<Term> objs = world.getUniverseForTermType("object");
        sortedObjectsInWorld = new ArrayList<String>(objs.size());
        for (Term t : objs) {
            String x = ((Constant) t).getName();
            sortedObjectsInWorld.add(x);
        }
        Collections.sort(sortedObjectsInWorld, new ObjectComparator());
    }

    public JMapViewer(World world) {
        this(world, 1, 1);
    }

    /*
    public void setWorld(World world) {
    this.world = world;
    }
     * */

    @Override
    public synchronized void paintComponent(Graphics g) {
        super.paintComponent(g);

        shapes.clear();
        boundingBoxesForObjectNames.clear();

        Graphics2D graphics = (Graphics2D) g;
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        graphics.setColor(Color.white);
        graphics.fill(new Rectangle2D.Float(0, 0, getWidth(), getHeight()));

        currentTransform = new AffineTransform();

        if (centerOnPlayer) {
            double playerX = xx(world.getPlayerPosition().getX()) - 102 * PLAYER_IMAGE_SCALE / 2;
            double playerY = yy(world.getPlayerPosition().getZ()) - 102 * PLAYER_IMAGE_SCALE / 2;
            currentTransform.translate(internalMapWidth * 3 / 4 - playerX, internalMapHeight * 3 / 4 - playerY);
        } else {
            currentTransform.translate(internalMapWidth * (widthFactor - 1) / 2, internalMapHeight * (heightFactor - 1) / 2);
        }

        if (alignWithPlayer) {
            currentTransform.rotate(GeometryTools.getAngle(world.getPlayerOrientation(), new Orientation(0, 0, 1)), xx(world.getPlayerPosition().getX()), yy(world.getPlayerPosition().getZ()));
        }

        graphics.transform(currentTransform);

        synchronized (world) {
            paintRooms(graphics);

            graphics.setColor(Color.black);
            drawBackgroundLines(graphics);

            if (showAxisLabels) {
                paintLabels(graphics);
            }

            paintObjects(graphics);

            if (showPlayerIcon) {
                paintPlayer(graphics);
            }

            for (MapAnnotationPlugin plugin : annotationPlugins) {
                plugin.paint(graphics, this);
            }
        }
    }

    private void paintPlayer(Graphics2D graphics) {
        BufferedImage img = playerImg;

        AffineTransform mine = new AffineTransform();

        // step 3: rotate around center of translated image
        mine.concatenate(AffineTransform.getRotateInstance(
                -GeometryTools.getAngle(world.getPlayerOrientation(), new Orientation(0, 0, 1)),
                xx(world.getPlayerPosition().getX()), yy(world.getPlayerPosition().getZ())));

        // step 2: translate to player position
        mine.translate(xx(world.getPlayerPosition().getX()) - 102 * PLAYER_IMAGE_SCALE / 2, yy(world.getPlayerPosition().getZ()) - 102 * PLAYER_IMAGE_SCALE / 2);

        // step 1: scale down to fit grid size
        mine.scale(PLAYER_IMAGE_SCALE, PLAYER_IMAGE_SCALE);

        graphics.drawImage(img, mine, null);
    }

    private int getObjectTypePaintingOrder(String type) {
        if (objectTypePaintingOrder.containsKey(type)) {
            return objectTypePaintingOrder.get(type);
        } else {
            return objectTypePaintingOrder.get("other");
        }
    }

    private void paintObjects(Graphics2D graphics) {
        for (String x : sortedObjectsInWorld) {
            String type = world.getType(x);
            Position pos = world.getPosition(x);
            Orientation ori = world.getOrientation(x);
            int ox = 0, oz = 0;

            int x1 = 0, y1 = 0, x2 = 0, y2 = 0;

            if (ori != null) {
                // rooms are objects, but have no orientation
                Orientation oriAbs = ori.abs();
                x1 = xx(pos.getX() + 0.5 * oriAbs.getZ());
                y2 = yy(pos.getZ() - 0.5 * oriAbs.getX());
                x2 = xx(pos.getX() - 0.5 * oriAbs.getZ());
                y1 = yy(pos.getZ() + 0.5 * oriAbs.getX());  // NB: y1 <= y2
                ox = (int) oriAbs.getX();
                oz = (int) oriAbs.getZ();
            }

            if (type.equals("wall")) {
                graphics.setColor(Color.black);
                graphics.setStroke(new BasicStroke(5));
                graphics.drawLine(x1, y1, x2, y2);

                addNamedShape(rectangleForLine(x1, y1, x2, y2, 5), x);
            } else if (type.equals("door")) {
                String state = world.getState(x);

                graphics.setColor(state.equals("closed") ? CLOSED_DOOR : OPEN_DOOR);
                graphics.setStroke(new BasicStroke(5));
                graphics.drawLine(x1 + 3 * oz, y1 + 3 * ox, x2 - 5 * oz, y2 - 5 * ox);

                graphics.setColor(Color.black);
                graphics.setStroke(new BasicStroke(1));
                graphics.drawRect(x1 - 2 * ox, y1 - 2 * oz,
                        x2 - x1 + 4 * ox, y2 - y1 + 4 * oz);

                addNamedShape(new Rectangle2D.Double(x1 - 2 * ox, y1 - 2 * oz,
                        x2 - x1 + 4 * ox, y2 - y1 + 4 * oz), x);
            } else if (type.equals("safe")) {
                int SIZE = GRID / 5;
                String state = world.getState(x);

                graphics.setColor(Color.blue);

                int startx = x1 - 1, starty = y1 - 1;
                int width = SIZE * 4 + 5, height = SIZE;

                // this will not work for safe orientations other than [-1,0,0]
                startx -= 43;
                starty += 4;
                height = GRID - 7;

                if (state.equals("safe-state-open")) {
                    graphics.setStroke(new BasicStroke(1));
                    graphics.drawLine(startx, starty, startx + width, starty);
                    graphics.drawLine(startx, starty, startx, starty + height);
                    graphics.drawLine(startx, starty + height, startx + width, starty + height);
                } else {
                    graphics.setStroke(new BasicStroke(2));
                    graphics.drawRect(startx, starty, width, height);
                }

                // add badge for safe state
                int badgeR = 20;
                int badgeX = startx + width - badgeR / 2;
                int badgeY = starty + height - badgeR / 2;

                String safeState = world.getState(x).substring(11);

                graphics.setStroke(new BasicStroke(1));

                if ("open".equals(safeState)) {
                    graphics.setColor(Color.white);
                    graphics.fillOval(badgeX, badgeY, badgeR, badgeR);

                    graphics.setColor(Color.blue);
                    graphics.drawOval(badgeX, badgeY, badgeR, badgeR);
                } else {
                    graphics.setColor(Color.blue);
                    graphics.fillOval(badgeX, badgeY, badgeR, badgeR);

                    graphics.setColor(Color.white);
                    graphics.drawString(safeState, badgeX + badgeR / 4, badgeY + badgeR * 3 / 4);
                }

                addNamedShape(new Rectangle2D.Double(startx, starty, width, height), x);
            } else if (type.equals("picture")) {
                int size = 3;                
                graphics.setColor(Color.red);
                graphics.setStroke(new BasicStroke(size));

                int xx1 = x1 - (size + 2) * (int) ori.getX();
                int yy1 = y1 - (size + 2) * (int) ori.getZ();
                int xx2 = x2 - (size + 2) * (int) ori.getX();
                int yy2 = y2 - (size + 2) * (int) ori.getZ();

                graphics.drawLine(xx1, yy1, xx2, yy2);
                addNamedShape(rectangleForLine(xx1, yy1, xx2, yy2, size), x);
            } else if (type.equals("windowwide")) {                
                // windows are tricky; transform orientations to make them behave the same as pictures
                if (ori.getX() == 0.0 && ori.getZ() == -1.0) {
                    // south --> west
                    ori = new Orientation(-1.0, 0.0, 0.0);
                } else if (ori.getX() == 0.0 && ori.getZ() == 1.0) {
                    // north --> east
                    ori = new Orientation(1.0, 0.0, 0.0);
                } else if (ori.getX() == 1.0 && ori.getZ() == 0.0) {
                    // east --> south
                    ori = new Orientation(0.0, 0.0, -1.0);
                } else {
                    // west --> north
                    ori = new Orientation(0.0, 0.0, 1.0);
                }
                
                // now recompute coordinates as in the beginning
                if (ori != null) {
                    Orientation oriAbs = ori.abs();
                    if (ori.getX() == -1) {
                	x1 = (int) (xx(pos.getX() - 0.5 * oriAbs.getZ()) + 5);
                	x2 = (int) (xx(pos.getX() - 0.5 * oriAbs.getZ()) + 5);
                    } else {
                        x1 = xx(pos.getX() + 0.5 * oriAbs.getZ());
                        x2 = (xx(pos.getX() - 0.5 * oriAbs.getZ()));                	
                    }
                    if (ori.getZ() == 1) {
                	y1 = yy(pos.getZ() + 0.5 * oriAbs.getX()) + 6;  
                	y2 = yy(pos.getZ() - 0.5 * oriAbs.getX()) + 6;                	
                    } else {
                	y1 = yy(pos.getZ() + 0.5 * oriAbs.getX());  
                	y2 = yy(pos.getZ() - 0.5 * oriAbs.getX());
                    }
                    ox = (int) oriAbs.getX();
                    oz = (int) oriAbs.getZ();
                }
                
                // finally draw them the same way as doors, but in cyan
                graphics.setColor(Color.cyan);
                graphics.setStroke(new BasicStroke(4));
                graphics.drawLine(x1 + 3 * oz, y1 + 3 * ox, x2 - 5 * oz, y2 - 5 * ox);
                addNamedShape(new Rectangle2D.Double(x1 - 2 * ox, y1 - 2 * oz, x2 - x1 + 4 * ox, y2 - y1 + 4 * oz), x);
            } else if (type.equals("button") || type.equals("stateless-button")) {
                String buttonColorString = world.getValueAsString(x, "color");
                Color buttonColor = Color.blue;

                if ("green".equals(buttonColorString)) {
                    buttonColor = Color.green;
                } else if ("red".equals(buttonColorString)) {
                    buttonColor = Color.red;
                } else if ("yellow".equals(buttonColorString)) {
                    buttonColor = Color.yellow;
                }

                graphics.setColor(buttonColor);

                float sizefactor = 0.3f;
                int size = (int) (GRID * 0.3);
                int startx = xx(pos.getX() - sizefactor / 2 * oz) - 2 * (int) ori.getX() - size;
                int starty = yy(pos.getZ() - sizefactor / 2 * ox) - 2 * (int) ori.getZ() - size;


                if (ori.getX() < 0) {
                    startx += size;
                }

                if (ori.getZ() < 0) {
                    starty += size;
                }


                graphics.fillRect(startx, starty, size, size);

                graphics.setColor(Color.black);
                graphics.setStroke(new BasicStroke(1));
                graphics.drawRect(startx, starty, size, size);

                addNamedShape(new Rectangle2D.Double(startx, starty, size, size), x);
            } else if (type.equals("alarm")) {
                int size = (int) (GRID * 0.7);
                Rectangle2D r = new Rectangle2D.Double(xx(pos.getX()) - size / 2, yy(pos.getZ()) - size / 2,
                        size, size);

                String state = world.getState(x);
                graphics.setColor(state.equals("alarmed") ? ALARM_COLOR_ON : ALARM_COLOR_OFF);
                graphics.fill(r);

                graphics.setColor(Color.black);
                graphics.setStroke(new BasicStroke(1));
                graphics.draw(r);

                addNamedShape(r, x);
            } else {
                // landmarks

                if (pos != null) {
                    try {
                        paintImage(graphics, xx(pos.getX() + 0.5), yy(pos.getZ() + 0.25), type);
                        addNamedShape(new Rectangle2D.Double(xx(pos.getX() + 0.25), yy(pos.getZ()), GRID * 0.5, GRID * 0.5), x);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private BufferedImage readFromResource(String resource) {
        try {
            return ImageIO.read(getClass().getClassLoader().getResourceAsStream(resource));
        } catch (IOException ex) {
            System.err.println("Couldn't load image file: " + resource);
            System.exit(1);
            return null;
        }
    }

    private void paintImage(Graphics2D g, int x, int y, String landmarkType) throws IOException {
        BufferedImage img = null;

        if (landmarkType.equals("flower")) {
            img = flowerImg;
        } else if (landmarkType.equals("chair")) {
            img = chairImg;
        } else if (landmarkType.equals("couch")) {
            img = couchImg;
        } else if (landmarkType.equals("lamp")) {
            img = lampImg;
        } else if (landmarkType.equals("trophy")) {
            img = trophyImg;
        } else if (landmarkType.equals("table")) {
        	img = tableImg;
        } else if (landmarkType.equals("dresser")) {
        	img = dresserImg;
        } else if (landmarkType.equals("bed")) {
        	img = bedImg;
        }

        if (img != null) {
            g.drawImage(img, x, y, null);
        } else {
            // unknown landmark type
            g.setColor(UNKNOWN_OBJECT_COLOR);
            g.fillRect(x, y, GRID / 2, GRID / 2);

            g.setColor(Color.black);
            g.setStroke(new BasicStroke(1));
            g.drawRect(x, y, GRID / 2, GRID / 2);
        }
    }

    private void addNamedShape(Shape shape, String name) {
        shapes.add(new NamedShape(shape, name));
        boundingBoxesForObjectNames.put(name, shape.getBounds2D());
    }

    public synchronized Rectangle2D getBoundingBox(String objectName) {
        return boundingBoxesForObjectNames.get(objectName);
    }

    public Point2D getCenter(String objectName) {
        Rectangle2D bb = getBoundingBox(objectName);

        return new Point2D.Double(bb.getCenterX(), bb.getCenterY());
    }

    private Shape rectangleForLine(int x1, int y1, int x2, int y2, int strokewidth) {
        return new Rectangle2D.Double(x1 - strokewidth / 2, y1 - strokewidth / 2, x2 - x1 + strokewidth, y2 - y1 + strokewidth);
    }

    private void drawBackgroundLines(Graphics2D graphics) {
        graphics.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER, 10.0f, new float[]{1, 4}, 0.0f));

        for (int x = map.getMinX(); x <= map.getMaxX(); x++) {
            graphics.drawLine(xx(x), yy(map.getMinY()), xx(x), yy(map.getMaxY()));
        }

        for (int y = map.getMinY(); y <= map.getMaxY(); y++) {
            graphics.drawLine(xx(map.getMinX()), yy(y), xx(map.getMaxX()), yy(y));
        }
    }

    private void paintRooms(Graphics2D graphics) {
        for (int x = map.getMinX(); x < map.getMaxX(); x++) {
            for (int y = map.getMinY(); y < map.getMaxY(); y++) {
                if (map.get(x, y) != null) {
                    String room = map.get(x, y).getName();
                    String roomColor = world.getValueAsString(room, "color");

                    Color color = ROOM_GRAY;
                    if ("red".equals(roomColor)) {
                        color = ROOM_RED;
                    } else if ("green".equals(roomColor)) {
                        color = ROOM_GREEN;
                    } else if ("blue".equals(roomColor)) {
                        color = ROOM_BLUE;
                    }

                    graphics.setColor(color);

                    drawFilledRect(graphics, x + 1, y, x + 2, y + 1);
                }
            }
        }
    }

    private void drawFilledRect(Graphics2D graphics, int x1, int y1, int x2, int y2) {
        if (y1 <= y2) {
            graphics.fill(new Rectangle2D.Double(xx(x1), yy(y2), GRID * (x2 - x1), GRID * (y2 - y1)));
        } else {
            // at the moment this is never the case, since drawFilledRect() only gets called for y2 = y1 + 1
            graphics.fill(new Rectangle2D.Double(xx(x1), yy(y1), GRID * (x2 - x1), GRID * (y1 - y2)));
        }
    }

    public int xx(double x) {
        return (int) (GRID * (0.5 + map.getMaxX() - x));
    }

    public int yy(double y) {
        return (int) (GRID * (0.5 + map.getMaxY() - y));
    }

    public void addMouseOverObjectListener(MouseOverObjectListener listener) {
        mouseOverObjectListeners.add(listener);
    }

    public void addMapAnnotationPlugin(MapAnnotationPlugin plugin) {
        annotationPlugins.add(plugin);
    }

    public void mouseDragged(MouseEvent e) {
    }

    public void mouseMoved(MouseEvent e) {
        Point2D originalPoint = null;

        try {
            originalPoint = currentTransform.inverseTransform(new Point2D.Float(e.getX(), e.getY()), null);
        } catch (NoninvertibleTransformException ex) {
            // this shouldn't happen because we only use invertible transforms
            Logger.getLogger(JMapViewer.class.getName()).log(Level.SEVERE, null, ex);
        }

        computeObjectsUnderMouse(originalPoint.getX(), originalPoint.getY());

        if (!currentObjectsUnderMouse.equals(previousObjectsUnderMouse)) {
            for (MouseOverObjectListener listener : mouseOverObjectListeners) {
                listener.mouseEnteredObject((int) originalPoint.getX(), (int) originalPoint.getY(), currentObjectsUnderMouse);
            }

            previousObjectsUnderMouse = currentObjectsUnderMouse;
            currentObjectsUnderMouse = new HashSet<String>();
        }
    }

    private void computeObjectsUnderMouse(double x, double y) {
        Rectangle2D cover = new Rectangle2D.Double(x - 1, y - 1, 3, 3);

        currentObjectsUnderMouse.clear();

        for (NamedShape it : shapes) {
            if (it.shape.contains(cover) || it.shape.intersects(cover)) {
                currentObjectsUnderMouse.add(it.name);
            }
        }
    }

    private void paintLabels(Graphics2D graphics) {
        FontMetrics fm = graphics.getFontMetrics();
        graphics.setColor(Color.black);

        // x labels
        int yForXlabels = internalMapHeight - GRID / 2 + LABELHEIGHT; // - GRID/2;
        int startx = GRID / 2;

        for (int x = map.getMinX(); x <= map.getMaxX(); x++) {
            int inverseX = map.getMaxX() - (x - map.getMinX());
            if (inverseX == map.getMinX() || (inverseX % 5 == 0) || inverseX == map.getMaxX()) {
                String label = Integer.toString(inverseX);
                Rectangle2D rect = fm.getStringBounds(label, graphics);
                graphics.drawString(label, (int) (startx + GRID * (x - map.getMinX()) + rect.getX() - rect.getWidth() / 2), yForXlabels);
            }
        }

        // y labels
        int xForYlabels = GRID / 2 - LABELWIDTH;
        int starty = internalMapHeight - (GRID / 2);

        for (int y = map.getMinY(); y <= map.getMaxY(); y++) {
            if (y == map.getMinY() || (y % 5 == 0) || y == map.getMaxY()) {
                String label = Integer.toString(y);
                Rectangle2D rect = fm.getStringBounds(label, graphics);
                graphics.drawString(label, xForYlabels,
                        (int) (starty - GRID * (y - map.getMinY()) - rect.getY() - rect.getHeight() / 2));
            }
        }
    }

    public void setShowAxisLabels(boolean x) {
        showAxisLabels = x;
    }

    public void setShowPlayerIcon(boolean showPlayerIcon) {
        this.showPlayerIcon = showPlayerIcon;
    }

    private static class NamedShape {

        public Shape shape;
        public String name;

        public NamedShape(Shape shape, String name) {
            this.shape = shape;
            this.name = name;
        }
    }

    private class ObjectComparator implements Comparator<String> {

        public int compare(String o1, String o2) {
            return getObjectTypePaintingOrder(world.getType(o1)) - getObjectTypePaintingOrder(world.getType(o2));
        }
    }

    /**
     * Call repaint() after this.
     */
    public void setAlignWithPlayer(boolean alignWithPlayer) {
        this.alignWithPlayer = alignWithPlayer;
    }

    /**
     * Call repaint() after this.
     */
    public void setCenterOnPlayer(boolean centerOnPlayer) {
        this.centerOnPlayer = centerOnPlayer;
    }
}
